<?php

/*
 * Project: Serpent - the PHP compiling template engine
 * Copyright (C) 2009 Christoph Erdmann
 * 
 * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version.
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110, USA
 */
	
include( dirname(__FILE__).'/serpentResourceAbstract.class.php' );

class Serpent
    {
    // main config
    public $compile_dir = 'templates_compiled/';
    public $force_compile = false;
	public $autoescape = true;
	public $default_resource = 'file';
	public $vars = array();
	protected $charset = 'utf-8';
	
	// all mapped methods
	public $_mapping = array();
	
	// just for captures
	public $_capture_stack = array();

	// for inheritance
	protected $_template_stack = array();
	protected $_block_content = array();
	protected $_block_name = array();
	
	// the compiler object
	protected $_compiler = null;
	
	// holds the resources
	protected $_resource = array();

	// the main method to render a template
	public function render($tpl, $resource_handler = null)
        {
		// add trailing slash to compile_dir if not exists
		if (substr($this->compile_dir, -1) != '/') $this->compile_dir .= '/'; 
		
		// check if compile dir exists
		if (!file_exists($this->compile_dir)) throw new Exception('compile_dir "'.$this->compile_dir.'" does not exist.');
		if (!is_writeable($this->compile_dir)) throw new Exception('compile_dir "'.$this->compile_dir.'" is not writeable.');

		// add to template stack
		$this->_template_stack[] = $this->_render($tpl, $resource_handler);

		// escape vars
		if ($this->autoescape) $this->vars = $this->_escape($this->vars, $this->charset);
		
		// render data 
		extract($this->vars, EXTR_REFS);
		ob_start();
		
		// include the extended templates
		// foreach would not work here because an included template could fill the template stack
		// (and foreach just works with a copy of the array and would not recognize the new template)
		while (count($this->_template_stack) > 0)
			{
			include( array_shift($this->_template_stack) );
			
			// throw away the output of templates that only extend
			if ( count($this->_template_stack) != 0) ob_clean();
			}
		
		return ob_get_clean();
        }
       
	public function addResource($name, $inst)
		{
		$this->_resource[$name] = $inst;
		}

	public function addMapping($old, $new)
		{
		$this->_mapping[$old] = $new;
		}
	
	public function setCharset($charset)
		{
		$this->charset = $charset;
		}
	
	// makes sure that a valid filename will be used for the compilation file
	protected function cleanFilename($tpl)
		{
		return preg_replace('=[^a-z0-9_-]=i', '%', $tpl);
		}
	
	// checks if a template has to be compiled and returns the path to the compiled template
	protected function _render($tpl, $resource_handler = null)
		{
		// fall back to standard ressource
		if (is_null($resource_handler))
			{
			$resource_handler = $this->default_resource;
			}
		
		// check existence of resource handler
		if (!isset($this->_resource[$resource_handler])) throw new Exception('resource handler "'.$resource_handler.'" does not exist.');
		
		// init resource handler
		$resource = $this->_resource[$resource_handler];
		
		// the name of the compiled template
		$compiled_tpl = $this->compile_dir . $this->cleanFilename( $resource_handler ) . '/' . $this->cleanFilename( $resource->getTemplateId($tpl) );
		
		// force compile
		if ($this->force_compile)
			{
			$this->_compile( $tpl, $resource, $compiled_tpl);
			}

		// check if a compiled template exists
		if (!file_exists( $compiled_tpl ))
			{
			$this->_compile( $tpl, $resource, $compiled_tpl);
			return $compiled_tpl;
			}
		
		// compare timestamp of tpl and compiled file
		$compiled_tpl_time	= filemtime( $compiled_tpl );
		$raw_tpl_mtime = $resource->getTimestamp( $tpl );
		if ($compiled_tpl_time != $raw_tpl_mtime)
			{
			$this->_compile( $tpl, $resource, $compiled_tpl);
			}

		return $compiled_tpl;
		}
		
	// creates the compiled template
    protected function _compile( $tpl, $resource, $compiled_tpl)
        {
		$source		= $resource->getTemplate( $tpl );
		$timestamp	= $resource->getTimestamp( $tpl );
		
		if (is_null($this->_compiler))
			{
			include_once( dirname(__FILE__).'/serpentCompiler.class.php' );
			$this->_compiler = new serpentCompiler($this);
			}
		$compiled = $this->_compiler->compile($source);
  
		// if path to compiled template does not exist create it
		if (!file_exists( dirname($compiled_tpl) )) mkdir( dirname($compiled_tpl), 0777, true );

		// write compiled template
		file_put_contents($compiled_tpl, $compiled);
		
		// clean compiled template
		file_put_contents($compiled_tpl, php_strip_whitespace($compiled_tpl));
		
		// touch it to synch the mtime of the original and the compiled template
		touch($compiled_tpl, $timestamp);
		}

	// used for the mapped function "block"
	protected function _block($name)
		{
		// put the actual top name on top to use it on endblock() as variable name
		$this->_block_name[] = $name;

		// return only the first value of this block ever
		if (!isset( $this->_block_content[$name] ))
			{
			ob_start();
			return true;
			}
		else return false;
		}

	// used for the mapped function "endblock"
	protected function _endblock()
		{
		// get the actual block name
		$name = array_pop($this->_block_name);
		
		// return only the first value of this block ever
		if ( !isset($this->_block_content[$name]) )
			{
			$this->_block_content[$name] = ob_get_clean();
			}
		
		return $this->_block_content[$name];
		}
			
	// used for the mapped function "escape"
	protected function _escape($var, $charset = null)
		{
		if (is_null($charset)) $charset = $this->charset;

		if (is_array($var))
			{
			foreach ($var as $key=>$value)
				{
				$var[$key] = $this->_escape($value, $charset);
				}
			}
		else
			{
			if (is_string($var))
				$var = htmlspecialchars($var, ENT_QUOTES, $charset);
			}
		return $var;
		}

	// used for the mapped function "unescape"
	protected function _unescape($var)
		{
		if (is_array($var))
			{
			foreach ($var as $key=>$value)
				{
				$var[$key] = $this->_unescape($value);
				}
			}
		else
			{
			if (is_string($var))
				$var = htmlspecialchars_decode($var, ENT_QUOTES);
			}
		return $var;
		}
	}
